//
// Created by gogoqiu on 2022/1/12.
//
#include <wx/wx.h>
#include "src/ids.h"
#include <libssh2.h>
#include <netdb.h>
#include "src/action_event.h"
#include "md5/md5.hpp"

#define MY_SCP_HOST "git.my.host"
#define MY_SCP_PORT 22222
#define MY_SCP_USER "gogoqiu"
#define MY_SCP_PASS "512512ZoPoqiu"
#define MY_SCP_PATH "/mnt/db"
//#define MY_SCP_PATH "/tmp/123"
//same with the md5sum definition
#define BLOCK_SIZE 4096

static void addLog( wxWindow *parent, const wxString& log ){
    ActionEvent event( parent, TOKEN_LOG, log );
    event.Post();
}

static void scpPercent( wxWindow *parent, int percent ){
    //
    int * n = new int;
    *n = percent;
    ActionEvent event( parent, TOKEN_SCP_PERCENT, n );
    event.Post();
}

static int waitSocket ( int socket_fd, LIBSSH2_SESSION *session )
{
    struct timeval timeout;
    int rc;
    fd_set fd;
    fd_set *writeFd = nullptr;
    fd_set *readFd = nullptr;
    int dir;

    timeout.tv_sec = 10;
    timeout.tv_usec = 0;

    FD_ZERO ( &fd );
    FD_SET ( socket_fd, &fd );

    /* now make sure we wait in the correct direction */
    dir = libssh2_session_block_directions ( session );
    if ( dir & LIBSSH2_SESSION_BLOCK_INBOUND )
        readFd = &fd;
    if ( dir & LIBSSH2_SESSION_BLOCK_OUTBOUND )
        writeFd = &fd;
    rc = select ( socket_fd + 1, readFd, writeFd, nullptr, &timeout );
    return rc;
}

int scp(wxWindow *parent, const char *localFile, const char *scpPath )
{
    struct md5_ctx ctx;
    md5_init_ctx (&ctx);
    //
    //login
    //write
    //
    unsigned long hostAddr;
    int sock, i, auth_pw = 1;
    struct sockaddr_in sin;
    const char *fingerprint;
    LIBSSH2_SESSION *session;
    LIBSSH2_CHANNEL *channel;
    //libssh2_struct_stat fileInfo;
    int rc;
    libssh2_struct_stat_size got = 0;

    FILE *local;
    struct stat64 fileInfo;

    char mem[BLOCK_SIZE];
    size_t nRead;
    char *ptr;
    bool ok = false;
#ifdef WIN32
    WSADATA wsadata;
	int err;

	err = WSAStartup ( MAKEWORD ( 2, 0 ), &wsadata );
	if ( err != 0 ) {
		fprintf ( stderr, "WSAStartup failed with error: %d\n", err );
		return 1;
	}
#endif
    addLog( parent, _T("starting scp..." ));
    rc = libssh2_init ( 0 );

    if ( rc ) {
        fprintf ( stderr, "libssh2 initialization failed (%d)\n", rc );
        return 1;
    }

    /* Ultra basic "connect to port 22 on localhost"
     * Your code is responsible for creating the socket establishing the
     * connection
     */
    sock = socket ( AF_INET, SOCK_STREAM, 0 );

    sin.sin_family = AF_INET;
    sin.sin_port = htons ( MY_SCP_PORT );
    //sin.sin_addr.s_addr = inet_addr("");
    hostent *host = gethostbyname ( MY_SCP_HOST );
    memmove ( & ( sin.sin_addr ), host->h_addr, host->h_length );

    if ( connect ( sock, ( struct sockaddr* ) ( &sin ),
                   sizeof ( struct sockaddr_in ) ) != 0 ) {
        addLog( parent, _T("failed to connect!" ) );
        return -1;
    }

    /* Create a session instance
     */
    session = libssh2_session_init();

    if ( !session )
        return -1;

    /* ... start it up. This will trade welcome banners, exchange keys,
     * and setup crypto, compression, and MAC layers
     */
    rc = libssh2_session_handshake ( session, sock );

    if ( rc ) {
        addLog( parent, _T("Failure establishing SSH session: %d\n") );
        return -1;
    }
    //
    local = fopen64 (localFile, "rb" );
    if ( !local ) {
        fprintf (stderr, "Can't open local file %s\n", localFile );
        return -1;
    }

    stat64 (localFile, &fileInfo );

    /* At this point we haven't yet authenticated.  The first thing to do
     * is check the hostKey's fingerprint against our known hosts Your app
     * may have it hard coded, may go to a file, may present it to the
     * user, that's your call
     */
    fingerprint = libssh2_hostkey_hash ( session, LIBSSH2_HOSTKEY_HASH_SHA1 );

    fprintf ( stderr, "Fingerprint: " );
    for ( i = 0; i < 20; i++ ) {
        fprintf ( stderr, "%02X ", ( unsigned char ) fingerprint[i] );
    }
    fprintf ( stderr, "\n" );

    if ( auth_pw ) {
        /* We could authenticate via password */
        if ( libssh2_userauth_password ( session, MY_SCP_USER, MY_SCP_PASS ) ) {

            addLog ( parent, _T( "Authentication by password failed.\n" ));
            goto shutdown;
        }
    } else {
        /* Or by public key */
#define HOME_DIR "/home/username/"
        if ( libssh2_userauth_publickey_fromfile ( session, MY_SCP_USER,

                                                   HOME_DIR ".ssh/id_rsa.pub",
                                                   HOME_DIR ".ssh/id_rsa",
                                                   MY_SCP_PASS ) ) {
            fprintf ( stderr, "\tAuthentication by public key failed\n" );
            goto shutdown;
        }
    }
/* Send a file via scp. The mode parameter must only have permissions! */
    //channel = libssh2_scp_send64 (session, scpPath, fileInfo.st_mode & 0777, ( unsigned long ) fileInfo.st_size );
    //timespec t = fileInfo.st_mtim;
    //timer_t tt;
    channel = libssh2_scp_send64(session, scpPath,
                                 fileInfo.st_mode & 0777, fileInfo.st_size, fileInfo.st_mtim.tv_sec, fileInfo.st_atim.tv_sec );

    if ( !channel ) {
        char *errmsg;
        int errLen;
        int err = libssh2_session_last_error ( session, &errmsg, &errLen, 0 );
        fprintf ( stderr, "Unable to open a session: (%d) %s\n", err, errmsg );
        goto shutdown;
    }

    fprintf ( stderr, "SCP session waiting to send file\n" );
    //count:fileInfo.st_size
    //done:
    __off_t done;
    int percent;
    do {
        nRead = fread (mem, 1, sizeof ( mem ), local );
        if (nRead <= 0 ) {
            /* end of file */
            break;
        }
        ptr = mem;

        do {
            /* write the same data over and over, until error or completion */
            rc = libssh2_channel_write ( channel, ptr, nRead );
            if ( rc < 0 ) {
                fprintf ( stderr, "ERROR %d\n", rc );
                break;
            } else {
                /* rc indicates how many bytes were written this time */
                ptr += rc;
                nRead -= rc;
            }
            //scpPercent
        } while ( nRead );
        //
        done +=nRead;
        percent = done * 100 / fileInfo.st_size;
        scpPercent( parent, percent );
    } while ( true );

    fprintf ( stderr, "Sending EOF\n" );
    libssh2_channel_send_eof ( channel );

    fprintf ( stderr, "Waiting for EOF\n" );
    libssh2_channel_wait_eof ( channel );

    fprintf ( stderr, "Waiting for channel to close\n" );
    libssh2_channel_wait_closed ( channel );
    libssh2_channel_free ( channel );
    channel = nullptr;
    ok= true;
shutdown:
    if ( session ) {
        libssh2_session_disconnect ( session, "Normal Shutdown, Thank you for playing" );
        libssh2_session_free ( session );
    }
#ifdef WIN32
    closesocket ( sock );
#else
    close ( sock );
#endif
    if ( local )
        fclose ( local );
    if( ok )
        fprintf ( stderr, "all done\n" );
    libssh2_exit();
    return 0;
}

static int md5sum( const char* path ){
    //md5_stream
    struct md5_ctx ctx;
    md5_init_ctx (&ctx);
    //md5_process_block ( buffer, BLOCKSIZE, &ctx );
    //md5_process_bytes (buffer, sum, &ctx);
    //md5_finish_ctx ( &ctx, resblock );
    return 0;
}

//buffer, long
static int scp_process_block( LIBSSH2_CHANNEL *channel, const char *buffer, size_t nRead ){
    int rc;
    //char* ptr;
    do {
        /* write the same data over and over, until error or completion */
        rc = libssh2_channel_write ( channel, buffer, nRead );
        if ( rc < 0 ) {
            fprintf ( stderr, "ERROR %d\n", rc );
            //break;
            return -1;
        } else {
            /* rc indicates how many bytes were written this time */
            buffer += rc;
            nRead -= rc;
        }
        //scpPercent
    } while ( nRead );
    return 0;
}

int scp_md5sum( wxWindow *parent, const char *localFile, const char *scpPath, char* md5sum ){
    unsigned char bin_result[16];
    struct md5_ctx ctx;
    md5_init_ctx (&ctx);
    unsigned long hostAddr;
    int sock, i, auth_pw = 1;
    struct sockaddr_in sin;
    const char *fingerprint;
    LIBSSH2_SESSION *session;
    LIBSSH2_CHANNEL *channel;
    //libssh2_struct_stat fileInfo;
    int rc;
    libssh2_struct_stat_size got = 0;

    FILE *local;
    struct stat64 fileInfo;

    char buffer[BLOCK_SIZE + 72];
    size_t sum;
    size_t nRead;
    char *ptr;
    bool ok = false;
    __off_t done = 0;
    int percent;
    int mode;
#ifdef WIN32
    WSADATA wsadata;
	int err;

	err = WSAStartup ( MAKEWORD ( 2, 0 ), &wsadata );
	if ( err != 0 ) {
		fprintf ( stderr, "WSAStartup failed with error: %d\n", err );
		return 1;
	}
#endif
    addLog( parent, _T("starting scp..." ));
    rc = libssh2_init ( 0 );

    if ( rc ) {
        fprintf ( stderr, "libssh2 initialization failed (%d)\n", rc );
        return 1;
    }

    /* Ultra basic "connect to port 22 on localhost"
     * Your code is responsible for creating the socket establishing the
     * connection
     */
    sock = socket ( AF_INET, SOCK_STREAM, 0 );

    sin.sin_family = AF_INET;
    sin.sin_port = htons ( MY_SCP_PORT );
    //sin.sin_addr.s_addr = inet_addr("");
    hostent *host = gethostbyname ( MY_SCP_HOST );
    memmove ( & ( sin.sin_addr ), host->h_addr, host->h_length );

    if ( connect ( sock, ( struct sockaddr* ) ( &sin ),
                   sizeof ( struct sockaddr_in ) ) != 0 ) {
        addLog( parent, _T("failed to connect!" ) );
        return -1;
    }

    /* Create a session instance
     */
    session = libssh2_session_init();

    if ( !session )
        return -1;

    /* ... start it up. This will trade welcome banners, exchange keys,
     * and setup crypto, compression, and MAC layers
     */
    rc = libssh2_session_handshake ( session, sock );

    if ( rc ) {
        addLog( parent, _T("Failure establishing SSH session: %d\n") );
        return -1;
    }
    //
    local = fopen64 (localFile, "rb" );
    if ( !local ) {
        fprintf (stderr, "Can't open local file %s\n", localFile );
        return -1;
    }

    stat64 ( localFile, &fileInfo );
    struct stat file_info;
    stat ( localFile, &file_info );
    /* At this point we haven't yet authenticated.  The first thing to do
     * is check the hostKey's fingerprint against our known hosts Your app
     * may have it hard coded, may go to a file, may present it to the
     * user, that's your call
     */
    fingerprint = libssh2_hostkey_hash ( session, LIBSSH2_HOSTKEY_HASH_SHA1 );

    fprintf ( stderr, "Fingerprint: " );
    for ( i = 0; i < 20; i++ ) {
        fprintf ( stderr, "%02X ", ( unsigned char ) fingerprint[i] );
    }
    fprintf ( stderr, "\n" );

    if ( auth_pw ) {
        /* We could authenticate via password */
        if ( libssh2_userauth_password ( session, MY_SCP_USER, MY_SCP_PASS ) ) {

            addLog ( parent, _T( "Authentication by password failed.\n" ));
            goto shutdown;
        }
    } else {
        /* Or by public key */
#define HOME_DIR "/home/username/"
        if ( libssh2_userauth_publickey_fromfile ( session, MY_SCP_USER,

                                                   HOME_DIR ".ssh/id_rsa.pub",
                                                   HOME_DIR ".ssh/id_rsa",
                                                   MY_SCP_PASS ) ) {
            fprintf ( stderr, "\tAuthentication by public key failed\n" );
            goto shutdown;
        }
    }
/* Send a file via scp. The mode parameter must only have permissions! */
    //channel = libssh2_scp_send64 (session, scpPath, fileInfo.st_mode & 0777, ( unsigned long ) fileInfo.st_size );
    //timespec t = fileInfo.st_mtim;
    //timer_t tt;
    //int mode,
    //channel = libssh2_scp_send64(session, scpPath,
                                 //fileInfo.st_mode & 0777, fileInfo.st_size, fileInfo.st_mtim.tv_sec, fileInfo.st_atim.tv_sec );
                                 mode = fileInfo.st_mode & 0777;
    printf( "%d, %d\n", fileInfo.st_mode, mode );
    channel = libssh2_scp_send64(session, scpPath,
                                 511, fileInfo.st_size, fileInfo.st_mtim.tv_sec, fileInfo.st_atim.tv_sec );

    if ( !channel ) {
        char *errmsg;
        int errLen;
        int err = libssh2_session_last_error ( session, &errmsg, &errLen, 0 );
        fprintf ( stderr, "Unable to open a session: (%d) %s\n", err, errmsg );
        goto shutdown;
    }

    addLog( parent,  _T( "SCP session waiting to send file\n" ) );
    //count:fileInfo.st_size
    //done:

    while (true)
    {
        /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
        computation function processes the whole buffer so that with the
        next round of the loop another block can be read.  */
        size_t n;
        sum = 0;
        /* Read block.  Take care for partial reads.  */
        while (true)
        {
            //
            n = fread ( buffer + sum, 1, BLOCK_SIZE - sum, local );
            //if n<0
            sum += n;
            if (sum == BLOCK_SIZE)
                break;
            if (n == 0)
            {
                /* Check for the error flag IFF N == 0, so that we don't
                exit the loop after a partial read due to e.g., EAGAIN
                or EWOULDBLOCK.  */
                if ( ferror (local) )
                    return 1;
                goto process_partial_block;
            }
            /* We've read at least one byte, so ignore errors.  But always
            check for EOF, since feof may be true even though N > 0.
            Otherwise, we could end up calling fread after EOF.  */
            if ( feof (local) )
                goto process_partial_block;
        }
        /* Process buffer with BLOCK_SIZE bytes.  Note that
        BLOCK_SIZE % 64 == 0
        */
        //求一整块数据的md5
        md5_process_block ( buffer, BLOCK_SIZE, &ctx );
        //post it to scp
        if( scp_process_block( channel, buffer, BLOCK_SIZE )== -1 ){
            //error
            goto shutdown;
        }
        done +=BLOCK_SIZE;
        percent = done * 100 / fileInfo.st_size;
        //
        scpPercent( parent, percent );
    }
process_partial_block:;
    /* Process any remaining bytes.  */
    if (sum > 0) {
        md5_process_bytes(buffer, sum, &ctx);
        scp_process_block( channel, buffer, sum );
    }
    /* Construct result in desired memory.  */
    md5_finish_ctx ( &ctx, bin_result );

    fprintf ( stderr, "Sending EOF\n" );
    libssh2_channel_send_eof ( channel );

    fprintf ( stderr, "Waiting for EOF\n" );
    libssh2_channel_wait_eof ( channel );

    fprintf ( stderr, "Waiting for channel to close\n" );
    libssh2_channel_wait_closed ( channel );
    libssh2_channel_free ( channel );
    channel = nullptr;
    ok= true;
shutdown:
    if ( session ) {
        libssh2_session_disconnect ( session, "Normal Shutdown, Thank you for playing" );
        libssh2_session_free ( session );
    }
#ifdef WIN32
    closesocket ( sock );
#else
    close ( sock );
#endif
    if ( local )
        fclose ( local );
    if( ok )
        fprintf ( stderr, "all done\n" );
    libssh2_exit();
    for( i=0; i<16; i++ ){
        sprintf( md5sum+i*2, "%02x", bin_result[i] );
    }
    wxString tmp( md5sum );
    addLog( parent, tmp );
    return 0;
}

int SimpleScpRep( wxWindow *parent, const char *localFile, char* md5sum ){
    //
    return scp_md5sum( parent, localFile, MY_SCP_PATH, md5sum );
}

int scpRep( wxWindow *parent, wxString path, wxString uuid, char* md5sum ){
    //
    const char *localFile = path.mb_str(wxConvUTF8);
    wxString tmp;
    tmp.sprintf( "%s/%s", MY_SCP_PATH, uuid );
    const char *dest = tmp.mb_str(wxConvUTF8);
    //MY_SCP_PATH
    wxString log;
    log.sprintf( "拷贝至%s", dest );
    addLog( parent, log );
    return scp_md5sum( parent, localFile, dest, md5sum );
}